﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace MartinRL.ProjectEuler.Sequences
{
    public class CollatzSequence : IEnumerable<long>
    {
        private readonly long startTerm;
        
        private CollatzSequence(long startTerm)
        {
            this.startTerm = startTerm;
        }

        public IEnumerator<long> GetEnumerator()
        {
            return new CollatzEnumerator(startTerm);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public ulong Count
        {
            get
            {
                var enumerator = new CollatzEnumerator(startTerm);

                ulong count = 0;

                while (enumerator.MoveNext())
                {
                    count++;
                }

                return count;
            }
        }

        public static CollatzSequence NewSequence(long startTerm)
        {
            return new CollatzSequence(startTerm);
        }

        public class CollatzEnumerator : IEnumerator<long>
        {
            private bool _shouldYieldFirstTerm = true;
            private long _current;
            public CollatzEnumerator(long startTerm)
            {
                _current = startTerm;
            }

            public void Dispose() { }

            public bool MoveNext()
            {
                if (_shouldYieldFirstTerm)
                {
                    _shouldYieldFirstTerm = false;

                    return true;
                }

                if (_current == 1) 
                    return false;

                UpdateCurrent();

                return true;
            }

            private void UpdateCurrent()
            {
                _current = CalculateNext(_current);
            }

            internal static long CalculateNext(long term)
            {
                return term.IsEven() ? term / 2 : term * 3 + 1;
            }

            public void Reset()
            {
                throw new NotSupportedException();
            }

            public long Current
            {
                get { return _current; }
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }
        }
    }
}